﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Forms.Layout;
using System.Windows.Forms.VisualStyles;
using BGShell.Interop;
using WindowsFormsAero;

namespace BGShell.Components
{
    [System.ComponentModel.DesignerCategory("Code")]
    internal sealed partial class DataTipForm : DropShadowForm
    {
        private const int ScrollerHeight = 12;

        private const int ScrollUpItem = int.MinValue;
        private const int ScrollDownItem = int.MaxValue;

        private readonly DataTipItemCollection _items = new DataTipItemCollection();

        private DataTipForm _parent;
        private DataTipForm _child;

        private Boolean _childClosedByKeyboard;
        private Boolean _itemSelectedByKeyboard;

        private RepeatingHoverProvider _hover;
        private DataTipLayoutEngine _layout;
        private DataTipRenderer _renderer;

        private Int32 _scrollSpeed = SystemInformation.KeyboardSpeed;
        private Timer _scrollRepeatTimer;

        private bool _sendingActivateMessage;
        private int _expandedItem = -1;

        private Font _dialogFont;

        private DataTipForm(DataTipForm parent)
        {
            _parent = parent;

            _hover = new RepeatingHoverProvider(this);
            _hover.MouseHover += OnRepeatingMouseHover;

            _dialogFont = SystemFonts.DialogFont;

            DoubleBuffered = true;
            Font = _dialogFont;
            FormBorderStyle = FormBorderStyle.None;
            ShowInTaskbar = false;
            StartPosition = FormStartPosition.Manual;
        }

        public DataTipForm()
            : this(null)
        {
            DataTipMessageFilter.BeginDataTip(this);
        }

        protected override Padding DefaultPadding
        {
            get { return new Padding(1, 1, 1, 1); }
        }

        public DataTipItemCollection DataItems
        {
            get { return _items; }
        }

        public override LayoutEngine LayoutEngine
        {
            get
            {
                if (_layout == null)
                {
                    _layout = new DataTipLayoutEngine(this);
                }

                return _layout;
            }
        }

        protected override bool ShowWithoutActivation
        {
            get { return true; }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_parent == null)
                {
                    DataTipMessageFilter.EndDataTip(this);
                }

                if (_hover != null)
                {
                    _hover.Dispose();
                }

                if (_scrollRepeatTimer != null)
                {
                    _scrollRepeatTimer.Dispose();
                }

                if (_child != null)
                {
                    _child.Dispose();
                }

                if (_dialogFont != null)
                {
                    _dialogFont.Dispose();
                }
            }

            _hover = null;
            _child = null;
            _parent = null;
            _dialogFont = null;

            base.Dispose(disposing);
        }

        protected override void OnDeactivate(EventArgs e)
        {
            base.OnDeactivate(e);

            if (_child == null || _child.Disposing || _child.IsDisposed)
            {
                Dispose();
            }
        }

        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);

            if (_child == null)
            {
                Dispose();
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (_renderer == null)
            {
                _renderer = new DataTipRenderer(this);
            }

            _renderer.Draw(e.Graphics, _child != null);
        }

        protected override void WndProc(ref Message m)
        {
            const int RDW_INVALIDATE = 0x0001;
            const int RDW_FRAME = 0x0400;

            const int MA_NOACTIVATE = 0x0003;

            if (m.Msg == WindowMessages.WM_MOUSEACTIVATE)
            {
                m.Result = new IntPtr(MA_NOACTIVATE);
            }
            else if (m.Msg == WindowMessages.WM_NCACTIVATE)
            {
                if ((m.WParam != IntPtr.Zero) && !(_sendingActivateMessage) && (Owner != null))
                {
                    _sendingActivateMessage = true;

                    try
                    {
                        var ownerHandle = new HandleRef(Owner, Owner.Handle);
                        var ownerFormHandle = new HandleRef(null,
                            NativeMethods.GetAncestor(ownerHandle, AncestorType.Root));
                        
                        NativeMethods.SendMessage(
                            ownerFormHandle,
                            WindowMessages.WM_NCACTIVATE,
                            new IntPtr(1),
                            IntPtr.Zero);

                        NativeMethods.RedrawWindow(
                            ownerFormHandle,
                            IntPtr.Zero,
                            IntPtr.Zero,
                            RDW_INVALIDATE | RDW_FRAME);
                    }
                    finally
                    {
                        _sendingActivateMessage = false;
                    }
                }
            }
            else
            {
                base.WndProc(ref m);
            }
        }

        internal DataTipForm Child
        {
            get { return _child; }
        }

        internal DataTipForm BottomMostTip
        {
            get
            {
                var tip = this;

                while (tip._parent != null)
                {
                    tip = tip._parent;
                }

                return tip;
            }
        }

        internal DataTipForm TopMostTip
        {
            get
            {
                var f = this;

                while (f.Child != null)
                {
                    f = f.Child;
                }

                return f;
            }
        }

        private bool IsScrollItemSelected
        {
            get
            {
                return
                _layout.SelectedItemIndex == ScrollUpItem ||
                _layout.SelectedItemIndex == ScrollDownItem;
            }
        }

        private int SelectedItemIndex
        {
            get { return _layout.SelectedItemIndex; }
            set { _layout.SelectedItemIndex = value; }
        }

        private DataTipItem SelectedItem
        {
            get { return GetItem(_layout.SelectedItemIndex); }
        }

        private DataTipItem ExpandedItem
        {
            get { return GetItem(_expandedItem); }
        }

        private DataTipItem GetItem(int index)
        {
            if ((index < 0) || (index >= _items.Count))
            {
                return null;
            }

            return _items[index];
        }



        //
        // Keyboard Handling
        //

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if (e.KeyCode == Keys.ControlKey)
            {
                BottomMostTip.SetOpacityRecursive(0.1);
            }
            else if (e.KeyData == Keys.Up)
            {
                TopMostTip.KeyboardScrollUp();
            }
            else if (e.KeyData == Keys.PageUp)
            {
                TopMostTip.KeyboardPageUp();
            }
            else if (e.KeyData == Keys.Home)
            {
                TopMostTip.KeyboardHome();
            }
            else if (e.KeyData == Keys.Down)
            {
                TopMostTip.KeyboardScrollDown();
            }
            else if (e.KeyData == Keys.PageDown)
            {
                TopMostTip.KeyboardPageDown();
            }
            else if (e.KeyData == Keys.End)
            {
                TopMostTip.KeyboardEnd();
            }
            else if (e.KeyData == Keys.Right)
            {
                TopMostTip.KeyboardExpandChild();
            }
            else if (e.KeyData == Keys.Left)
            {
                TopMostTip.KeyboardClose();
            }
            else if (e.KeyData == Keys.Escape)
            {
                BottomMostTip.Dispose();
            }
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.ControlKey)
            {
                BottomMostTip.SetOpacityRecursive(1.0);
            }

            base.OnKeyUp(e);
        }

        private void KeyboardScrollDown()
        {
            if (!IsScrollItemSelected)
            {
                SelectedItemIndex++;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardScrollUp()
        {
            if (!IsScrollItemSelected)
            {
                SelectedItemIndex--;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardPageDown()
        {
            if (!IsScrollItemSelected)
            {
                int newSelected = SelectedItemIndex + _layout.VisibleItemCount;

                if (newSelected > _items.Count - 1)
                {
                    newSelected = _items.Count - 1;
                }

                SelectedItemIndex = newSelected;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardPageUp()
        {
            if (!IsScrollItemSelected)
            {
                int newSelected = SelectedItemIndex - _layout.VisibleItemCount;

                if (newSelected < 0)
                {
                    newSelected = 0;
                }

                SelectedItemIndex = newSelected;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardHome()
        {
            if (!IsScrollItemSelected)
            {
                SelectedItemIndex = 0;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardEnd()
        {
            if (!IsScrollItemSelected)
            {
                SelectedItemIndex = _items.Count - 1;
                SetCursorToSelectedItem();
            }
        }

        private void KeyboardClose()
        {
            if (_parent != null)
            {
                _parent._childClosedByKeyboard = true;
            }

            Dispose();
        }

        private void KeyboardExpandChild()
        {
            if ((SelectedItem != null) && (SelectedItem.Subitems.Count > 0))
            {
                SetCursorToItem(SelectedItemIndex, true);
            }
        }

        private void SetCursorToSelectedItem()
        {
            if ((SelectedItemIndex >= 0) && (SelectedItemIndex < _items.Count))
            {
                SetCursorToItem(SelectedItemIndex, false);
            }
        }

        private void SetCursorToItem(int item, bool intoExpander)
        {
            var rect =_layout.GetItemRect(item);
            var pt = rect.Location;

            pt.Y += (rect.Height / 2);

            if (intoExpander)
            {
                var expanderRect = _layout.GetExpanderRect(item);
                pt.X = expanderRect.Right - (expanderRect.Width / 2);
            }

            _itemSelectedByKeyboard = true;
            Cursor.Position = PointToScreen(pt);
        }

        private void SetOpacityRecursive(double opacity)
        {
            if (_child != null)
            {
                _child.SetOpacityRecursive(opacity);
            }

            Opacity = opacity;
        }

        private void HideChild()
        {
            if (_child != null)
            {
                _child.Dispose();
            }
        }

        private void ShowChild()
        {
            if (SelectedItem != ExpandedItem)
            {
                HideChild();

                if ((SelectedItem != null) && (SelectedItem.Subitems.Count > 0))
                {
                    _expandedItem = SelectedItemIndex;

                    _childClosedByKeyboard = false;
                    _child = new DataTipForm(this);

                    _child.Disposed += OnChildClosed;
                    _child.DataItems.AddRange(SelectedItem.Subitems);

                    _child.PerformLayout();
                    _child.Location = _layout.GetChildPopupLocation(_expandedItem, _child.Size);

                    _child.Show(Owner);
                    _child.Select();
                    _child.Focus();

                    Invalidate();
                }
            }
        }


        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Button == MouseButtons.Left)
            {
                PerformSelectedItemClick();
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (_layout.IsTooFarOut(e.Location))
            {
                Dispose();
            }
            else
            {
                var item = _layout.GetItemAt(e.Location) ?? -1;

                SelectedItemIndex = item;

                if (item != ScrollUpItem && item != ScrollDownItem)
                {
                    if (_itemSelectedByKeyboard && (item > -1))
                    {
                        var expanderRect = _layout.GetExpanderRect(item);

                        if (expanderRect.Contains(e.Location))
                        {
                            ShowChild();
                        }
                    }
                }

                _itemSelectedByKeyboard = false;
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (_scrollRepeatTimer != null)
            {
                _scrollRepeatTimer.Stop();
            }

            base.OnMouseUp(e);
        }

        internal void ProcessMouseMove(MouseEventArgs e)
        {
            OnMouseMove(e);
        }
        
        private void MouseScrollUp()
        {
            BeginScrollRepeat();
            _layout.FirstVisibleItemIndex--;
        }

        private void MouseScrollDown()
        {
            BeginScrollRepeat();
            _layout.FirstVisibleItemIndex++;
        }

        private void PerformSelectedItemClick()
        {
            if (SelectedItem != null)
            {
                ShowChild();
            }
            else if (SelectedItemIndex == ScrollUpItem)
            {
                MouseScrollUp();
            }
            else if (SelectedItemIndex == ScrollDownItem)
            {
                MouseScrollDown();
            }
        }

        private void BeginScrollRepeat()
        {
            if (_scrollRepeatTimer == null)
            {
                _scrollRepeatTimer = new Timer();
                _scrollRepeatTimer.Tick += OnScrollRepeatTick;
                _scrollRepeatTimer.Interval = SystemInformation.KeyboardDelay;
            }

            if (!_scrollRepeatTimer.Enabled)
            {
                _scrollRepeatTimer.Start();
            }
        }


        private void OnChildClosed(object sender, EventArgs e)
        {
            _child.Disposed -= OnChildClosed;
            _child = null;

            if (_childClosedByKeyboard)
            {
                SetCursorToItem(SelectedItemIndex, false);
            }

            _childClosedByKeyboard = false;
            _expandedItem = -1;

            Invalidate();
        }

        private void OnRepeatingMouseHover(object sender, MouseEventArgs e)
        {
            var item = _layout.GetItemAt(e.Location);

            if (item.HasValue)
            {
                var expanderRect = _layout.GetExpanderRect(item.Value);

                if (expanderRect.Contains(e.Location))
                {
                    ShowChild();
                }
            }
        }

        private void OnScrollRepeatTick(object sender, EventArgs e)
        {
            const int TotalSteps = 32;

            const double MinInterval = 33;
            const double MaxInterval = 400;
            
            const double StepSize = (MaxInterval - MinInterval) / TotalSteps;

            int interval = (int)(MaxInterval - (_scrollSpeed * StepSize));

            if (SelectedItemIndex == ScrollUpItem)
            {
                _scrollRepeatTimer.Interval = interval;
                MouseScrollUp();
            }
            else if (SelectedItemIndex == ScrollDownItem)
            {
                _scrollRepeatTimer.Interval = interval;
                MouseScrollDown();
            }
        }

    }
}
